package com.gbase8c.dmt.db.opengauss;

import com.gbase8c.dmt.db.object.FunctionObject;

import com.gbase8c.dmt.model.migration.config.Task;
import com.gbase8c.dmt.model.migration.dto.DataSourceDto;
import com.gbase8c.dmt.model.migration.dto.FunctionDto;
import com.gbase8c.dmt.model.migration.config.Type;
import com.gbase8c.dmt.model.migration.wrapper.KeywordsWrapper;
import com.google.common.collect.Maps;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class FunctionObjectImpl extends MetaImpl implements FunctionObject {

    private static Map<String, String> functionBodys = Maps.newHashMap();

    public static String key(String str) {
        if (str != null) {
            str = str.toLowerCase();
            Pattern pattern = Pattern.compile("\\s*|\t|\r|\n");
            Matcher matcher = pattern.matcher(str);
            return matcher.replaceAll("");
        } else {
            return null;
        }
    }

    static {
        String create_table_key =
                "i := 1;\n" +
                        "WHILE i <= table_count LOOP\n" +
                        "\tEXECUTE IMMEDIATE 'create table tb_' || to_char(i) || '(col int)';\n" +
                        "\ti:=i+1;\n" +
                        "END LOOP;\n" +
                        "RETURN 1;";
        String create_table_value =
                "i=1;\n" +
                        "while i<=table_count loop\n" +
                        "\texecute 'create table tb_' || i::varchar || '(col int)';\n" +
                        "\ti:=i+1;\n" +
                        "end loop;\n" +
                        "return 1;";
        functionBodys.put(key(create_table_key), create_table_value);

        String insert_table_key =
                "i := 1;\n" +
                        "WHILE i <= n LOOP\n" +
                        "\tEXECUTE IMMEDIATE 'insert into ' || to_char(table_name) || ' values(' || to_char(i) || ')';\n" +
                        "\ti:=i+1;\n" +
                        "END LOOP;\n" +
                        "RETURN 1;";
        String insert_table_value =
                "i:=1;\n" +
                        "while i <= n loop\n" +
                        "\texecute 'insert into ' || table_name || ' values(' || i::varchar || ')';\n" +
                        "\ti:=i+1;\n" +
                        "end loop;\n" +
                        "return 1;";
        functionBodys.put(key(insert_table_key), insert_table_value);

        String update_table_key =
                "EXECUTE IMMEDIATE 'update ' || to_char(table_name) || ' set ' || to_char(colname) || '=' || to_char(colname) || '+1' ;\n" +
                        "RETURN 1;";
        String update_table_value =
                "execute 'update ' || table_name || ' set ' || colname || '=' || colname || '+1';\n" +
                        "return 1;";
        functionBodys.put(key(update_table_key), update_table_value);

        String delete_table_key =
                "EXECUTE IMMEDIATE 'delete from ' || to_char(table_name) || ' where ' || to_char(colname) || '=' || to_char(i);\n" +
                        "RETURN 1;";
        String delete_table_value =
                "execute 'delete from ' || table_name || ' where ' || colname || '=' ||i::varchar;\n" +
                        "return 1;";
        functionBodys.put(key(delete_table_key), delete_table_value);
    }

    public FunctionObjectImpl(DataSourceDto dataSourceDto) {
        super(dataSourceDto);
    }

    @Override
    public List<String> getNames(Map<String, Object> params) {
        return null;
    }

    @Override
    public FunctionDto get(String name, Map<String, Object> params) {
        return null;
    }

    @Override
    public FunctionDto convert(FunctionDto functionDto, Map<String, Object> params) {
        Task task = functionDto.getTask();
        boolean preserveCase = task.getMigrateConfig().isPreserveCase();
        KeywordsWrapper keywordsWrapper = new KeywordsWrapper(task.getKeywordsConfig());
        String tarFunctionName = keywordsWrapper.getFunctionName(functionDto.getSchema(),
                functionDto.getName(),
                preserveCase);
        functionDto.setTarName(tarFunctionName);

        DataSourceDto srcDataSource = (DataSourceDto) params.get("srcDataSource");
        DataSourceDto tarDataSource = (DataSourceDto) params.get("tarDataSource");
        if (ObjectUtils.isEmpty(functionDto.getInitializable()) || functionDto.getInitializable()) {
            Boolean convertible = Boolean.TRUE;
            boolean needConverted = !srcDataSource.getDbType().equals(tarDataSource.getDbType());
            List<FunctionDto.Parameter> parameters = functionDto.getParamDtos();
            if (CollectionUtils.isNotEmpty(parameters)) {
                for (FunctionDto.Parameter parameter : parameters) {
                    convertible = convertible && convertType(parameter, needConverted);
                }
            }

            convertible = convertible && convertType(functionDto, needConverted);

            List<FunctionDto.Variable> variables = functionDto.getVarDtos();
            if (CollectionUtils.isNotEmpty(variables)) {
                for (FunctionDto.Variable variable : variables) {
                    convertible = convertible && convertType(variable, needConverted);
                }
            }

//            convertible = Boolean.TRUE;

            convertFunctionBody(functionDto, srcDataSource, tarDataSource);
            functionDto.setConvertible(convertible);

            if (functionDto.getConvertible()) {
                functionDto.setConvertMsg("已完成");
            } else {
                functionDto.setConvertMsg("失败");
            }

        } else {
            functionDto.setConvertible(false);
//            functionDto.setConvertible(true);
            functionDto.setConvertMsg(functionDto.getMsg());
        }
        return functionDto;
    }

    public void convertFunctionBody(FunctionDto functionDto, DataSourceDto srcDataSource, DataSourceDto tarDataSource) {
        String key = key(functionDto.getSrcFunctionBody());
        if (functionBodys.containsKey(key)) {
            functionDto.setTarFunctionBody(functionBodys.get(key));
        } else {
            functionDto.setTarFunctionBody(functionDto.getSrcFunctionBody());
        }
    }

    @Override
    public String sql(FunctionDto functionDto, Map<String, Object> params) {
        // 根据functionDto生成创建function的sql
        StringBuilder sb = new StringBuilder();
        String functionSql = null;
        if (functionDto.getConvertible()) {
            String tarSchema = functionDto.getTarSchema();
            String name = functionDto.getName();

            boolean preserveCase = functionDto.getTask().getMigrateConfig().isPreserveCase();

            sb.append("CREATE OR REPLACE FUNCTION ");
            sb.append(wrap(tarSchema, true)).append(".").append(wrap(name, preserveCase));
            sb.append("(");
            if (CollectionUtils.isNotEmpty(functionDto.getParamDtos())) {
                List<FunctionDto.Parameter> parameters = functionDto.getParamDtos();
                for (int i = 0; i < parameters.size(); i++) {
                    FunctionDto.Parameter parameter = parameters.get(i);
                    sb.append(parameter.getParamName()).append(" ");
                    List<FunctionDto.Mode> modes = parameter.getModes();
                    if (CollectionUtils.isNotEmpty(modes)) {
                        sb.append(StringUtils.join(modes, " ")).append(" ");
                    }
                    sb.append(tarType(parameter));
                    if (i < parameters.size() - 1) {
                        sb.append(",");
                    }
                    sb.append("\n");
                }
            }
            sb.append(")");
            sb.append(" ").append("RETURNS ").append(tarType(functionDto));
            sb.append(" ").append("AS").append(" ").append("$body$").append("\n");
            if (CollectionUtils.isNotEmpty(functionDto.getVarDtos())) {
                sb.append("declare").append("\n");
                List<FunctionDto.Variable> variables = functionDto.getVarDtos();
                for (FunctionDto.Variable variable : variables) {
                    sb.append(variable.getVarName()).append(" ").append(tarType(variable)).append("; ").append("\n");
                }
            }
            sb.append("BEGIN").append(" ").append("\n");
            sb.append(functionDto.getTarFunctionBody()).append(" ").append("\n");
            sb.append("END").append(";").append(" ").append("$body$").append(" ").append("LANGUAGE plpgsql").append(";");
            functionSql = sb.toString();
        }
        if (StringUtils.isBlank(functionSql)) {
            functionSql = functionDto.getSrcFuncText();
        }
        return functionSql;
    }

    public String tarType(Type type) {
        StringBuilder sb = new StringBuilder();
        String dataType = type.getTarDataType();
        sb.append(dataType);
        if (needSetLengthSet.contains(dataType)) {
            //如果说是numeric类型，是这个样子的 "a23" numeric(255,6)
            if (dataType.equals("numeric")) {
                Integer dataPrecision = type.getDataPrecision();
                Integer dataScale = type.getDataScale();
                if (dataPrecision != null) {
                    sb.append("(").append(dataPrecision);
                    if (dataScale != null) {
                        sb.append(", ").append(dataScale);
                    }
                    sb.append(")");
                }
            } else {
                //不是numeric字段的 是这个样子的 "a12" varbit(255)
                Integer dataLength = type.getDataLength();
                if (dataLength != null) {
                    sb.append("(").append(dataLength).append(")").append(" ");
                }
            }
        }
        return sb.toString();
    }
}
